﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Reflection;

using Lilium.LExpress;

namespace Lilium.Reflection
{
    public static class ObjectExtensions
    {
        public static void CopyProperties(this object src, object dst)
        {
            CopyProperties(src, dst, _ => true);
        }

        public static void CopyProperties(this object src, object dst, Func<PropertyInfo, bool> filter)
        {
            if (src == null) throw new ArgumentNullException("src");
            if (dst == null) throw new ArgumentNullException("dst");

            var srcType = src.GetType();
            var dstType = dst.GetType();

            foreach (var srcProperty in srcType.GetProperties())
            {
                var dstProperty = dstType.GetProperty(srcProperty.Name);
                if (dstProperty != null && filter(srcProperty))
                {
                    var value = srcProperty.GetValue(src, null);
                    dstProperty.SetValue(dst, value, null);
                }
            }
        }

        public static object DynamicInvoke(this object target, string methodName, IDictionary<string, object> parameterDictionary)
        {
            if (target == null) throw new ArgumentNullException("target");
            if (methodName == null) throw new ArgumentNullException("methodName");

            var targetType = target.GetType();
            var targetMethod = targetType.GetMethod(methodName);

            if (targetMethod == null)
                throw new InvalidOperationException(string.Format("Method {0} not found in class {1}", methodName, targetType));

            var parameters = ExtractParameters(targetMethod.GetParameters(), parameterDictionary);

            return targetMethod.Invoke(target, parameters);
        }

        public static object DynamicInvoke(this object target, string methodName, Type[] typeArguments, IDictionary<string, object> parameterDictionary)
        {
            if (target == null) throw new ArgumentNullException("target");
            if (methodName == null) throw new ArgumentNullException("methodName");
            if (typeArguments == null) throw new ArgumentNullException("typeArguments");

            var targetType = target.GetType();
            var targetMethod = targetType.GetMethod(methodName);

            var parameters = ExtractParameters(targetMethod.GetParameters(), parameterDictionary);

            return targetMethod.MakeGenericMethod(typeArguments).Invoke(target, parameters);
        }

        public static object DynamicGetValue(this object target, string propertyOrFieldName)
        {
            var expr = Expression.PropertyOrField(Expression.Constant(target), propertyOrFieldName);
            return expr.Eval();
        }

        private static object[] ExtractParameters(ParameterInfo[] methodParameters, IDictionary<string, object> parameterDictionary)
        {
            if (methodParameters == null) throw new ArgumentNullException("methodParameters");

            var paramsCount = methodParameters.Length;
            var parameters = new object[paramsCount];

            if (parameterDictionary != null)
                for (var i = 0; i < paramsCount; i++)
                {
                    object parameterValue;
                    if (parameterDictionary.TryGetValue(methodParameters[i].Name, out parameterValue))
                        parameters[i] = parameterValue;
                }
            return parameters;
        }
    }
}